import java.beans.Customizer;
import java.io.*;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;

import org.omg.CORBA.ORB;

public class BoxOfficeServer_Impl extends BoxOfficeServer_interfacePOA {
	private static int TotalNumberOfShows = 10;
	private boolean RECORD_STATUS = false; 
	
	private BufferedWriter out = null;
	private BufferedWriter out_update;
	private BufferedReader in;
	private File oldFile;
	private File newFile;
	
	private String fileName = "AllCustomers_Montreal.txt";
	private boolean firstTime = true;
	private boolean infoStored = false;
	private boolean success = false;
	/* <Key: ShowID, Data: Number Of Tickets Available> and default concurrency level is 16 which is enough */
	private ConcurrentHashMap<String,Integer>ShowsInfos = new ConcurrentHashMap<String,Integer>(TotalNumberOfShows);  
	
	private ORB orb;
	private String notify_msg1 = "Sorry, but you are not eligible to perform this operation!"; 
	private String notify_msg2 = "Cancelation Approved.";
	private enum server {MONTREAL,TORONTO,CALGARY,OTTAWA,VANCOUVER};
	
	private byte[] buffer = new byte[100];
	private DatagramSocket aSocket;
	private DatagramPacket request;
	private DatagramPacket reply_canExchange;
	private String canExchange = null;
	private InetAddress aServer;
	private int serverPort = 5000;

	
	//private boolean search = false;
	
	public BoxOfficeServer_Impl() {
		super();
		
		/* these are all the shows that this BoxOfficeServer manage */
		ShowsInfos.put("120", 10);
		ShowsInfos.put("121", 30);
		ShowsInfos.put("122", 20);
		ShowsInfos.put("123", 50);
		ShowsInfos.put("124", 5);
		ShowsInfos.put("125", 10);
		ShowsInfos.put("126", 15);
		ShowsInfos.put("127", 25);
		ShowsInfos.put("128", 4);
		ShowsInfos.put("129", 9);
		
		oldFile = new File(fileName);
	
		
	}
	
	public void setORB(ORB orb_value){
		orb = orb_value;
	}
	public void shutdown(){
		orb.shutdown(false);
		
	}
	
	
	public String reserve(int customerID, String showID, int numberOfTickets) {
		
		System.out.println("I reserved.");
		int new_numberOfTickets;
		
		do {
			int AvailableTickets = ShowsInfos.get(showID);
			if(AvailableTickets >= numberOfTickets){
			
				new_numberOfTickets = AvailableTickets - numberOfTickets;
				
			}
			else{
				return ("Sorry but only "+AvailableTickets+" ticket(s) is/are available(s).");
			}
			
			success = ShowsInfos.replace(showID, AvailableTickets, new_numberOfTickets);
			} while(!success);
		
			
			/* Add this customer with needed informations and "writeToFile" is synchronized methods  */
			infoStored = writeToFile(customerID, showID, numberOfTickets);

			if(infoStored) {
				infoStored = false;
				return "Approved.";
			
			}
			else return "\nFail to register!";
			
			
		}

	public String cancel(int customerID, String showID, int numberOfTickets) { 
		
		
		boolean eligible = searchInFileToCancel(customerID, showID, numberOfTickets);
		if(eligible){
			System.out.println("Everything is ok");
			
			/* updating the hash map by adding the returned # of tickets */
			do {
				int AvailableTickets = ShowsInfos.get(showID);
				int new_numberOfTickets = AvailableTickets + numberOfTickets;
				
				success = ShowsInfos.replace(showID, AvailableTickets, new_numberOfTickets);
				} while(!success);
			
			RECORD_STATUS = false;
		
			try{
			
			if(oldFile.exists()){
				
				if(oldFile.delete()) System.out.println("Deleted OK");
				else System.out.println("Deleted Not Ok.");
				
				if(newFile.renameTo(oldFile)){
					System.out.println("File Renamed Ok.");
					
					return notify_msg2;
					
				}
				else System.out.println("File Not Renamed.");
				
				
			}
			else System.out.println("File not exists!");
			}
			catch(Exception FileEx){
				System.out.println("Exception in File: "+FileEx.getMessage());
				return "Fail to cancel, try again later..";
			}
			
		}
		else{
			System.out.println("Something wrong (showID or number of tickets)");
			return "Sorry, but you are not eligible to perform this operation!";
		}
		
		RECORD_STATUS = false;
		return "";
	
	}
	
	
		
	
	/* Do not need any synchronisation since its only read data */
	public int check(String showID) {
		
		if(ShowsInfos.containsKey(showID)){
			/* return the remaining # of tickets for the given showID */
			return ShowsInfos.get(showID);
		}
		/* indicating that no such show exists */
		else return -1;
	}
	
	private synchronized boolean writeToFile(int customerID, String showID, int numberOfTickets){
		
		try {
			out = new BufferedWriter(new FileWriter(oldFile, true));
	
			if(firstTime){
				 out.write("|Customer ID | Show ID | Number Of Tickets| Date|\n");
				 out.write("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");
				 firstTime = false;
			 }
			
			  out.write(" " + Integer.toString(customerID) + "        ,");
		      out.write(showID + "       ,");
		      out.write(Integer.toString(numberOfTickets) + "               ,Date: "+ getDateTime() + "\n");
		      
		      out.close();
		      
		    } catch (FileNotFoundException ex) {
		      return (false);
		    } catch (IOException ex) {
		      return (false);
		    }
		    return (true);

		  }
	
	@SuppressWarnings("finally")
	private synchronized boolean searchInFileToCancel(int customerID, String showID, int numberOfTickets){
		
		String str_line = null;
		try{
			
			in = new BufferedReader(new FileReader(oldFile));
			newFile = new File("Updated_"+fileName);
			out_update = new BufferedWriter(new FileWriter(newFile));
			
			while((str_line = in.readLine()) != null){
				
				String saved = str_line;
				
				//System.out.println("the full line: "+str_line);
				//System.out.println("we have " + str_line.substring(1,7));
				
				/* get this customer ID record from server file if any */
				str_line = str_line.substring(1,7);
				
				if(str_line.equals(Integer.toString(customerID))){
					System.out.println("OK Found");
					
					String tokens[] = saved.split(",");
					
					//System.out.println("the tokens are: " + tokens[0]+ " & " + tokens[1] +" & "+ tokens[2]);
					
					tokens[0] = tokens[0].trim();
					tokens[1] = tokens[1].trim();
					tokens[2] = tokens[2].trim();
					
					System.out.println("the tokens after are: " + tokens[0]+ " & " + tokens[1] +" & "+ tokens[2]);
					
					/*  */
					if(tokens[1].equals(showID) && ( (Integer.parseInt(tokens[2]) >= numberOfTickets ))){
						
						System.out.println("OK to cancel for "+tokens[0]);
				
						int difference = Integer.parseInt(tokens[2]) - numberOfTickets;
						
						out_update.write(" " + tokens[0] + "        ,");
					    out_update.write(tokens[1] + "       ,");
					    /* new number of tickets*/
					    out_update.write(Integer.toString(difference) + "               ,Date: "+ getDateTime() + "\n");
						RECORD_STATUS = true;
						
					}
					else{
						System.out.println("Not ok to cancel for "+tokens[0]);
						String store = saved;
						//System.out.println("Store is: "+store);
						
						out_update.write(store+"\n");
					}
				}
				
				else {
					String store = saved;
					//System.out.println("Store is: "+store);
					out_update.write(store+"\n");
				}
				
			}
			out_update.close();
			return RECORD_STATUS;
		}
		
		catch(IOException IOex){
			System.out.println(IOex.getMessage());
			return RECORD_STATUS;
		}
		
		finally{
				return RECORD_STATUS;
		}
		
	}
	/**
	 * A modified version of "searchInFileToCancel" methods
	 * @param customerID
	 * @param showID
	 * @param numberOfTickets
	 * @return true = found or false = not found 
	 */
	@SuppressWarnings("finally")
	private synchronized boolean searchInFile(int customerID, String showID, int numberOfTickets){
		
		String str_line = null;
		try{
			
			in = new BufferedReader(new FileReader(oldFile));
			
			while((str_line = in.readLine()) != null){
				
				String saved = str_line;
				System.out.println("the full line: "+str_line);
				System.out.println("we have " + str_line.substring(1,7));
				/* get this customer ID record from server file if any */
				str_line = str_line.substring(1,7);
				
				if(str_line.equals(Integer.toString(customerID))){
					System.out.println("OK Found");
					String tokens[] = saved.split(",");
					System.out.println("the tokens are: " + tokens[0]+ " & " + tokens[1] +" & "+ tokens[2]);
					
					tokens[0] = tokens[0].trim();
					tokens[1] = tokens[1].trim();
					tokens[2] = tokens[2].trim();
					
					System.out.println("the tokens after are: " + tokens[0]+ " & " + tokens[1] +" & "+ tokens[2]);
					
					/* now check for the number of tickets  */
					if(tokens[1].equals(showID) && ( (Integer.parseInt(tokens[2]) == numberOfTickets ))){
						
						System.out.println("OK to exchange for "+tokens[0]);
						RECORD_STATUS = true;
					}
					else{
						System.out.println("Not ok to cancel for "+tokens[0]);
						String store = saved;
						System.out.println("Store is: "+store);
					}
				}
				
				else {
					String store = saved;
					System.out.println("Store is: "+store);
					//out_update.write(store+"\n");
				}
				
			}
			//out_update.close();
			return RECORD_STATUS;
		}
		
		catch(IOException IOex){
			System.out.println(IOex.getMessage());
			return RECORD_STATUS;
		}
		
		finally{
				return RECORD_STATUS;
		}
		
	}
	
	private static String getDateTime() {
     
		Calendar calendar = Calendar.getInstance();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        return dateFormat.format(calendar.getTime());
    }
	
	public void enableListener(){
		
		DatagramSocket aSocket = null;
		
		try{
			aSocket = new DatagramSocket(4444);
			byte[] buffer = new byte[100];

			while(true){
			
				DatagramPacket request = new DatagramPacket(buffer, buffer.length);
				aSocket.receive(request);
			
				MyThread NewExchange = new MyThread(request);
				NewExchange.start();
			}	
		}
		catch(SocketException exc){
			System.out.println("Error in socket: " + exc.getMessage());
		}
		catch(IOException ioexc){
			System.out.println("Error in I/O: " + ioexc.getMessage());
		}
		finally{
			if(aSocket != null) aSocket.close();
		}
		
	}
	
	//the marshaled form does not include types because we assume that the receiver side
	//have a knowledge about the order of data field.
	private synchronized byte[] marshalle(int customerID, String desiredShowID, int desiredTickets){
		
		//we have to include the CustomerID here
		String IDString = Integer.toString(customerID);
		
		int length = desiredShowID.length();
		String firstPart = (Integer.toString(length)).concat(desiredShowID);
		String secondPart = firstPart.concat(Integer.toString(desiredTickets));		
		String finalString = IDString.concat(secondPart);
		
		System.out.println("Final string is		: "+finalString);  //just for debug
		byte marshaledData[] = finalString.getBytes();
		
		return marshaledData;
	}
	/**
	 * prefix: is used to direct the operation to the appropriate server based on customer request
	 */
	public String exchange(int customerID, String reservedShowID,
			String prefix, int reservedTickets, String desiredShowID,
			int desiredTickets) {
		
		/* 1st step is to make sure the customer have a registered show (just scanning the file)  */
		RECORD_STATUS = false;
		boolean exists = searchInFile(customerID, reservedShowID, reservedTickets);
		if(exists){
			
			try{
				buffer = new byte[100];
				aSocket = new DatagramSocket();
				byte message[] = marshalle(customerID, desiredShowID, desiredTickets);
				/* default address & port */
				aServer = InetAddress.getByName("127.0.0.1");
				serverPort = 5000;
				
				System.out.println("The Prefix: " + prefix);
				/* Direct the request to the appropriate server */
				synchronized (this) {
					switch(server.valueOf(prefix)){
						
						case MONTREAL: 
							System.out.println("To Montreal Server..");
							//aServer = InetAddress.getByName("192.168.1.102");
							aServer = InetAddress.getByName("127.0.0.1");
							serverPort = 4444;
						break;
						
						case TORONTO: 
							System.out.println("To Torontor Server..");
							//aServer = InetAddress.getByName("192.168.1.109");
							aServer = InetAddress.getByName("132.205.241.149");
							serverPort = 3000;
						break;
						
						case CALGARY: 
							System.out.println("To Calgary Server..");
							aServer = InetAddress.getByName("127.0.0.1");
							serverPort = 8000;
						break;
						
						case OTTAWA: 
							System.out.println("To Ottawa Server..");
							aServer = InetAddress.getByName("127.0.0.1");
							serverPort = 8500;
						break;
						
						case VANCOUVER: 
							System.out.println("To Vancouver Server..");
							aServer = InetAddress.getByName("127.0.0.1");
							serverPort = 9000;
						break;
						
						default: 
							System.out.println("Invalid Prefix");
						break;
					}
				}	
				//aServer = InetAddress.getByName("127.0.0.1");
				//int serverPort = 3000;
				request = new DatagramPacket(message, message.length,aServer,serverPort);
				aSocket.send(request);

				System.out.println("Send from sender is done.");

				reply_canExchange = new DatagramPacket(buffer, buffer.length);
				aSocket.receive(reply_canExchange);
				
				canExchange = new String(reply_canExchange.getData());		  /* Just used for debug */
				System.out.println(canExchange); 							  /* Just used for debug */
				System.out.println(reply_canExchange.getLength()); 			  /* Just used for debug */
				
				/* The reply_canExchange message will be "Approved." which is 9 characters in length 
				 * or any other messages indicating why not approved and all are not 9 characters in length */
				if(reply_canExchange.getLength() == 9){
					System.out.println("Reply back is: " + new String(reply_canExchange.getData()));
					
					/* Now after getting a confirmation reply we can cancel the reservation of that customer safely
					 * by updating both the data structure & the File*/
					String status = cancel(customerID, reservedShowID, reservedTickets);
					/* this will always return "Cancellation Approved."  
					 * as we checked that at the beginning */
					if(status.equals(notify_msg2)){ 
						return new String(reply_canExchange.getData());
					}
				}
				/* otherwise return what the other server indicate (e.g not enough tickets are available )*/
				return new String(reply_canExchange.getData());
								
			}
			catch(SocketException exc){
				System.out.println("Error in socket: " + exc.getMessage());
			}
			catch(IOException ioexc){
				System.out.println("Error in I/O: " + ioexc.getMessage());
			}
			finally{
				if(aSocket != null) aSocket.close();
			}

		}
		else return "You are not eligible to perform this exchange.";
		
		return "We face some difficulty in processing your request, try again later.";
	}
}

class MyThread extends Thread{
	
	private DatagramPacket aPacketRequest = null;
	private DatagramSocket aSocket = null;
	
	public MyThread(DatagramPacket aPacketRequest) {
		this.aPacketRequest = aPacketRequest;
	}
	
	private synchronized String[] un_marshalle(byte[] byte_receivedData){
		
		String dataCollection[] = {null,null,null};
		
		String receivedData = new String(byte_receivedData);
		String received_customerID = receivedData.substring(0,6);
		
		int showLength = Integer.parseInt(receivedData.substring(6,7));
		String desiredShowID = receivedData.substring(7,showLength+7);
		String desiredTickets = receivedData.substring(showLength+7,showLength+8);
		
		/* used only for debug */
		System.out.println("received data are: "+receivedData);
		System.out.println("customer ID reveived: "+received_customerID);
		System.out.println("show length is: "+showLength);
		System.out.println("showID is: "+desiredShowID);
		System.out.println("tickets is: "+desiredTickets);
	
		dataCollection[0] = received_customerID;
		dataCollection[1] = desiredShowID;
		dataCollection[2] = desiredTickets;
		
		return dataCollection;		
	}
	
	
	public void run(){
		
		try{
		
			aSocket = new DatagramSocket();	
			String infoCollection[] = un_marshalle(aPacketRequest.getData());
			
			//reply_status = Approved. or not
		   	String reply_status = new BoxOfficeServer_Impl().reserve(Integer.parseInt(infoCollection[0])
		   			, infoCollection[1], Integer.parseInt(infoCollection[2]));
		   	
		   	byte reply[] = reply_status.getBytes();
		   		
		   	DatagramPacket replyPacket = new DatagramPacket(reply,
			reply.length, aPacketRequest.getAddress(), aPacketRequest.getPort());
			aSocket.send(replyPacket);
		   	aSocket.close();
			
		   	System.out.println("Server has just send the reply.");
			
		}
		catch(SocketException exc){
			System.out.println("Error in socket: " + exc.getMessage());
		}
		catch(IOException ioexc){
			System.out.println("Error in I/O: " + ioexc.getMessage());
		}
		finally{
			if(aSocket != null) aSocket.close();
		}
	}
}